/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <limits.h>

#include <securec.h>

#include "parcel.h"

#define MAX_PARCEL_MALLOC_SIZE 1024

static void ParcelRecycle(Parcel *parcel);

Parcel CreateParcelWithData(const uint8_t *data, uint32_t size)
{
    Parcel parcel = CreateParcel(size);
    (void)ParcelWrite(&parcel, data, size);
    return parcel;
}

Parcel CreateParcel(uint32_t size)
{
    Parcel parcel;
    (void)memset_s(&parcel, sizeof(parcel), 0, sizeof(parcel));

    if (size == 0 || size > MAX_PARCEL_MALLOC_SIZE) {
        return parcel;
    }

    uint8_t *data = (uint8_t *)malloc(size);
    if (data == NULL) {
        return parcel;
    }

    (void)memset_s(data, size, 0, size);
    parcel.data = data;
    parcel.length = size;
    return parcel;
}

void DeleteParcel(Parcel *parcel)
{
    if (parcel == NULL) {
        return;
    }

    if (parcel->data != NULL) {
        (void)memset_s(parcel->data, parcel->length, 0, parcel->length);
        free(parcel->data);
        parcel->data = NULL;
    }

    parcel->length = 0;
    parcel->beginPos = 0;
    parcel->endPos = 0;
}

bool ParcelRead(Parcel *parcel, uint8_t *dst, uint32_t dataSize)
{
    if (parcel == NULL || dst == NULL || dataSize == 0) {
        return false;
    }

    if (parcel->beginPos > UINT_MAX - dataSize) {
        return false;
    }

    if (parcel->beginPos + dataSize > parcel->endPos) {
        return false;
    }

    if (memmove_s(dst, dataSize, parcel->data + parcel->beginPos, dataSize) != EOK) {
        return false;
    }
    parcel->beginPos += dataSize;
    return true;
}

bool ParcelWrite(Parcel *parcel, const uint8_t *src, uint32_t dataSize)
{
    if (parcel == NULL || src == NULL || dataSize == 0) {
        return false;
    }

    if (parcel->endPos > UINT_MAX - dataSize) {
        return false;
    }

    if (parcel->endPos + dataSize > parcel->length) {
        ParcelRecycle(parcel);
    }

    if (parcel->endPos + dataSize > parcel->length) {
        // currently not support auto increase
        return false;
    }

    if (memmove_s(parcel->data + parcel->endPos, dataSize, src, dataSize) != EOK) {
        return false;
    }
    parcel->endPos += dataSize;
    return true;
}

uint32_t GetParcelDataSize(const Parcel *parcel)
{
    if (parcel == NULL) {
        return 0;
    }
    if (parcel->endPos >= parcel->beginPos) {
        return parcel->endPos - parcel->beginPos;
    }
    return 0;
}

const uint8_t *GetParcelData(const Parcel *parcel)
{
    if (parcel == NULL) {
        return NULL;
    }
    return parcel->data + parcel->beginPos;
}

bool ParcelToDataBuffer(const Parcel *parcel, uint8_t *buffer, uint32_t *bufferSize)
{
    if (parcel == NULL || buffer == NULL || bufferSize == NULL) {
        return false;
    }

    const uint8_t *data = GetParcelData(parcel);
    uint32_t dataSize = GetParcelDataSize(parcel);
    if (memcpy_s(buffer, *bufferSize, data, dataSize) != EOK) {
        return false;
    }

    *bufferSize = dataSize;
    return true;
}

bool ParcelWriteUint8(Parcel *parcel, uint8_t src)
{
    return ParcelWrite(parcel, &src, sizeof(uint8_t));
}

bool ParcelWriteUint16(Parcel *parcel, uint16_t src)
{
    return ParcelWrite(parcel, (uint8_t *)&src, sizeof(uint16_t));
}

bool ParcelWriteUint32(Parcel *parcel, uint32_t src)
{
    return ParcelWrite(parcel, (uint8_t *)&src, sizeof(uint32_t));
}

bool ParcelWriteUint64(Parcel *parcel, uint64_t src)
{
    return ParcelWrite(parcel, (uint8_t *)&src, sizeof(uint64_t));
}

bool ParcelReadUint8(Parcel *parcel, uint8_t *dst)
{
    return ParcelRead(parcel, (uint8_t *)dst, sizeof(uint8_t));
}

bool ParcelReadUint16(Parcel *parcel, uint16_t *dst)
{
    return ParcelRead(parcel, (uint8_t *)dst, sizeof(uint16_t));
}

bool ParcelReadUint32(Parcel *parcel, uint32_t *dst)
{
    return ParcelRead(parcel, (uint8_t *)dst, sizeof(uint32_t));
}

bool ParcelReadUint64(Parcel *parcel, uint64_t *dst)
{
    return ParcelRead(parcel, (uint8_t *)dst, sizeof(uint64_t));
}

// private functions
static void ParcelRecycle(Parcel *parcel)
{
    if (parcel == NULL) {
        return;
    }

    if (parcel->data == NULL) {
        return;
    }

    if (parcel->beginPos == 0) {
        return;
    }

    if (parcel->endPos < parcel->beginPos) {
        return;
    }

    uint32_t contentSize = parcel->endPos - parcel->beginPos;

    if (memmove_s(parcel->data, parcel->length, parcel->data + parcel->beginPos, contentSize) != EOK) {
        return;
    }

    parcel->beginPos = 0;
    parcel->endPos = contentSize;
}